package com.skivingcloud.admin.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.skivingcloud.admin.sys.entity.SysUser;
import com.skivingcloud.admin.sys.service.SysUserService;
import com.skivingcloud.common.utils.ReturnT;
import com.skivingcloud.validation.exceptions.ValidateCaptchaException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.AuthorizationServiceException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.csrf.CsrfException;
import org.springframework.security.web.csrf.InvalidCsrfTokenException;
import org.springframework.security.web.csrf.MissingCsrfTokenException;
import org.springframework.security.web.session.SessionInformationExpiredEvent;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * @author hushouquan
 */
@Component
public class MyAuthenticationHandler implements AuthenticationSuccessHandler,
        AuthenticationFailureHandler,
        LogoutSuccessHandler,
        SessionInformationExpiredStrategy,
        AccessDeniedHandler,
        AuthenticationEntryPoint{
    private static final Logger log = LoggerFactory.getLogger(MyAuthenticationHandler.class);
    public static final String APPLICATION_JSON_CHARSET_UTF_8 = "application/json;charset=UTF-8";
    public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    private SysUserService sysUserService;

    public MyAuthenticationHandler(SysUserService sysUserService) {
        this.sysUserService = sysUserService;
    }

    /**
     * 认证失败
     * @param request 请求参数
     * @param response 返回
     * @param authException 认证异常
     * @throws IOException io异常
     */
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        String detailMessage = authException.getClass().getSimpleName() + " " + authException.getLocalizedMessage();
        if(authException instanceof InsufficientAuthenticationException){
            detailMessage = "请登陆后再访问";
        }
        if(authException instanceof ValidateCaptchaException){
            detailMessage = authException.getMessage();
        }
        response.setContentType(APPLICATION_JSON_CHARSET_UTF_8);
        response.setStatus(HttpStatus.OK.value());
        response.getWriter().println(OBJECT_MAPPER.writeValueAsString(ReturnT.error(HttpStatus.UNAUTHORIZED.value(), detailMessage)));
        log.error(detailMessage, authException);
    }
    
    /**
     * 权限不足时的处理
     * @param request 请求参数
     * @param response 返回
     * @param accessDeniedException 认证异常
     * @throws IOException io异常
     */
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        String detailMessage;
        if (accessDeniedException instanceof MissingCsrfTokenException) {
            detailMessage = "缺少CSRF TOKEN,请从表单或HEADER传入";
        } else if (accessDeniedException instanceof InvalidCsrfTokenException) {
            detailMessage = "无效的CSRF TOKEN";
        } else if (accessDeniedException instanceof CsrfException) {
            detailMessage = accessDeniedException.getLocalizedMessage();
        } else if (accessDeniedException instanceof AuthorizationServiceException) {
            detailMessage = AuthorizationServiceException.class.getSimpleName() + " " + accessDeniedException.getLocalizedMessage();
        } else {
            detailMessage = "对不起，您没有此操作权限";
        }
        response.setContentType(APPLICATION_JSON_CHARSET_UTF_8);
        response.setStatus(HttpStatus.OK.value());
        response.getWriter().println(OBJECT_MAPPER.writeValueAsString(ReturnT.error(HttpStatus.FORBIDDEN.value(), detailMessage)));
    }
    
    /**
     * 认证失败时的处理
     * @param request req
     * @param response res
     * @param exception 异常
     * @throws IOException 异常
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
        response.setContentType(APPLICATION_JSON_CHARSET_UTF_8);
        response.setStatus(HttpStatus.OK.value());
        String detailMessage;
        if (exception instanceof ValidateCaptchaException) {
            detailMessage = exception.getLocalizedMessage();
        } else {
            detailMessage = "登陆失败";
        }
        response.getWriter().println(OBJECT_MAPPER.writeValueAsString(ReturnT.error(detailMessage)));
        log.error(exception.getLocalizedMessage(), exception);
    }
    
    /**
     * 认证成功时的处理
     * @param request req
     * @param response res
     * @param authentication 认证结果
     * @throws IOException 异常
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        response.setContentType(APPLICATION_JSON_CHARSET_UTF_8);
        response.setStatus(HttpStatus.OK.value());
        SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
        SysUser sysUser = sysUserService.getById(securityUser.getId());
        sysUser.setPassword(null);
        sysUser.setSalt(null);
        // SecurityContext在设置Authentication的时候并不会自动写入Session，读的时候却会根据Session判断，所以需要手动写入一次，否则下一次刷新时SecurityContext是新创建的实例。
        request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
        response.getWriter().println(OBJECT_MAPPER.writeValueAsString(ReturnT.ok(sysUser)));
    }
    
    /**
     * 登出成功处理
     * @param request req
     * @param response res
     * @param authentication 认证结果
     * @throws IOException 异常
     */
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        response.setContentType(APPLICATION_JSON_CHARSET_UTF_8);
        response.setStatus(HttpStatus.OK.value());
        response.getWriter().println(OBJECT_MAPPER.writeValueAsString(ReturnT.ok("注销成功")));
    }
    
    /**
     * 同用户在其他地方登录
     * @param event event
     * @throws IOException 异常
     */
    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException {
        String message = "该会话过期或账号已从其他设备登陆,如果不是您自己的操作请及时修改密码";
        final HttpServletResponse response = event.getResponse();
        response.setContentType(APPLICATION_JSON_CHARSET_UTF_8);
        response.setStatus(HttpStatus.OK.value());
        response.getWriter().println(OBJECT_MAPPER.writeValueAsString(ReturnT.error(HttpStatus.UNAUTHORIZED.value(), message)));
    }
}
